﻿using System;
using System.Reflection;
using System.Reflection.Emit;

namespace Core.Tools
{
    public static class PointerBuilder
    {
        public unsafe static void BuilderPointerDLL()
        {
            var AssName = new AssemblyName(
                "Core.Pointer");

            var AssBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(
                    AssName,
                    AssemblyBuilderAccess.RunAndSave);

            var ModBuilder = AssBuilder.DefineDynamicModule(
                "Core.Pointer",
                "Core.Pointer.dll");

            var PointerTypeBuilder = ModBuilder.DefineType(
                "Core.Tools.Pointer",
                TypeAttributes.Public | TypeAttributes.Abstract |
                TypeAttributes.AutoClass | TypeAttributes.AnsiClass |
                TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);

            #region -- Unbox(object)。

            var UnBoxMethodBuilder = PointerTypeBuilder.DefineMethod(
                "UnBox",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(IntPtr),
                new Type[] { typeof(object) });

            var UnBoxILGenerator = UnBoxMethodBuilder.GetILGenerator();

            UnBoxILGenerator.Emit_Ldarg(0);
            UnBoxILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Unbox(TypedReference)。

            var UnBox2MethodBuilder = PointerTypeBuilder.DefineMethod(
                "UnBox",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(IntPtr),
                new Type[] { typeof(TypedReference) });

            var UnBox2ILGenerator = UnBox2MethodBuilder.GetILGenerator();
            
            UnBox2ILGenerator.Emit_Ldarga(0);
            UnBox2ILGenerator.Emit(OpCodes.Ldind_I);
            UnBox2ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Box(IntPtr)。

            var BoxMethodBuilder = PointerTypeBuilder.DefineMethod(
                "Box",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(object),
                new Type[] { typeof(IntPtr) });

            var BoxILGenerator = BoxMethodBuilder.GetILGenerator();

            BoxILGenerator.Emit_Ldarg(0);
            BoxILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- GetTypeHandle(IntPtr)。

            var GetTypeHandleMethodBuilder = PointerTypeBuilder.DefineMethod(
                "GetTypeHandle",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(IntPtr),
                new Type[] { typeof(IntPtr) });

            var GetTypeHandleILGenerator = GetTypeHandleMethodBuilder.GetILGenerator();

            GetTypeHandleILGenerator.Emit_Ldarg(0);
            GetTypeHandleILGenerator.Emit(OpCodes.Ldind_I);
            GetTypeHandleILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- GetTypeHandle(object)。

            var GetTypeHandle2MethodBuilder = PointerTypeBuilder.DefineMethod(
                "GetTypeHandle",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(IntPtr),
                new Type[] { typeof(object) });

            var GetTypeHandle2ILGenerator = GetTypeHandle2MethodBuilder.GetILGenerator();

            GetTypeHandle2ILGenerator.Emit_Ldarg(0);
            GetTypeHandle2ILGenerator.Emit(OpCodes.Ldind_I);
            GetTypeHandle2ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Cast(IntPtr, IntPtr)。

            var CastMethodBuilder = PointerTypeBuilder.DefineMethod(
                "Cast",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(object),
                new Type[] { typeof(IntPtr), typeof(IntPtr) });

            var CastILGenerator = CastMethodBuilder.GetILGenerator();

            CastILGenerator.Emit_Ldarg(0);
            CastILGenerator.Emit_Ldarg(1);
            CastILGenerator.Emit(OpCodes.Stind_I);
            CastILGenerator.Emit_Ldarg(0);
            CastILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Cast(object, IntPtr)。

            var Cast2MethodBuilder = PointerTypeBuilder.DefineMethod(
                "Cast",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(object),
                new Type[] { typeof(object), typeof(IntPtr) });

            var Cast2ILGenerator = Cast2MethodBuilder.GetILGenerator();

            Cast2ILGenerator.Emit_Ldarg(0);
            Cast2ILGenerator.Emit(OpCodes.Call, UnBoxMethodBuilder);
            Cast2ILGenerator.Emit_Ldarg(1);
            Cast2ILGenerator.Emit(OpCodes.Stind_I);
            Cast2ILGenerator.Emit_Ldarg(0);
            Cast2ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Cast(IntPtr, Type)。

            var Cast3MethodBuilder = PointerTypeBuilder.DefineMethod(
                "Cast",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(object),
                new Type[] { typeof(IntPtr), typeof(Type) });

            var Cast3ILGenerator = Cast3MethodBuilder.GetILGenerator();

            var Cast3HandleLocal = Cast3ILGenerator.DeclareLocal(typeof(RuntimeTypeHandle));

            Cast3ILGenerator.Emit_Ldarg(0);
            Cast3ILGenerator.Emit_Ldarg(1);
            Cast3ILGenerator.Emit(OpCodes.Callvirt, typeof(Type).GetProperty("TypeHandle").GetGetMethod());
            Cast3ILGenerator.Emit_Stloc(Cast3HandleLocal.LocalIndex);
            Cast3ILGenerator.Emit_Ldloca(Cast3HandleLocal.LocalIndex);
            Cast3ILGenerator.Emit(OpCodes.Call, typeof(RuntimeTypeHandle).GetProperty("Value").GetGetMethod());
            Cast3ILGenerator.Emit(OpCodes.Stind_I);
            Cast3ILGenerator.Emit_Ldarg(0);
            Cast3ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Cast(IntPtr, Type)。

            var Cast4MethodBuilder = PointerTypeBuilder.DefineMethod(
                "Cast",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(object),
                new Type[] { typeof(object), typeof(Type) });

            var Cast4ILGenerator = Cast4MethodBuilder.GetILGenerator();

            Cast4ILGenerator.Emit_Ldarg(0);
            Cast4ILGenerator.Emit_Ldarg(1);
            Cast4ILGenerator.Emit(OpCodes.Call, Cast3MethodBuilder);
            Cast4ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Cast<T>(IntPtr)。

            var Cast5MethodBuilder = PointerTypeBuilder.DefineMethod(
                "Cast",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                null,
                new Type[] { typeof(IntPtr) });

            var Cast5GenericsParameters = Cast5MethodBuilder.DefineGenericParameters("T");

            Cast5MethodBuilder.SetReturnType(Cast5GenericsParameters[0]);

            var Cast5ILGenerator = Cast5MethodBuilder.GetILGenerator();

            Cast5ILGenerator.Emit_Ldarg(0);
            Cast5ILGenerator.Emit(OpCodes.Ldtoken, Cast5GenericsParameters[0]);
            Cast5ILGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"));
            Cast5ILGenerator.Emit(OpCodes.Call, Cast3MethodBuilder);
            Cast5ILGenerator.Emit(OpCodes.Unbox_Any, Cast5GenericsParameters[0]);
            Cast5ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- Cast<T>(object)。

            var Cast6MethodBuilder = PointerTypeBuilder.DefineMethod(
                "Cast",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                null,
                new Type[] { typeof(object) });

            var Cast6GenericsParameters = Cast6MethodBuilder.DefineGenericParameters("T");

            Cast6MethodBuilder.SetReturnType(Cast6GenericsParameters[0]);

            var Cast6ILGenerator = Cast6MethodBuilder.GetILGenerator();

            Cast6ILGenerator.Emit_Ldarg(0);
            Cast6ILGenerator.Emit(OpCodes.Ldtoken, Cast6GenericsParameters[0]);
            Cast6ILGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"));
            Cast6ILGenerator.Emit(OpCodes.Call, Cast3MethodBuilder);
            Cast6ILGenerator.Emit(OpCodes.Unbox_Any, Cast6GenericsParameters[0]);
            Cast6ILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- SizeOf<Type>()。

            var SizeOfMethodBuilder = PointerTypeBuilder.DefineMethod(
                "SizeOf",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(int),
                new Type[] { });

            var SizeOfGenericsParameters = SizeOfMethodBuilder.DefineGenericParameters("T");

            var SizeOfILGenerator = SizeOfMethodBuilder.GetILGenerator();
            
            SizeOfILGenerator.Emit(OpCodes.Sizeof, SizeOfGenericsParameters[0]);
            SizeOfILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- CopyMemory<IntPtr, IntPtr, uint>()。

            var CopyMemoryMethodBuilder = PointerTypeBuilder.DefineMethod(
                "CopyMemory",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(void),
                new Type[] { typeof(IntPtr), typeof(IntPtr), typeof(uint) });

            CopyMemoryMethodBuilder.DefineParameter(1, ParameterAttributes.None, "Source");
            CopyMemoryMethodBuilder.DefineParameter(2, ParameterAttributes.None, "Destination");
            CopyMemoryMethodBuilder.DefineParameter(3, ParameterAttributes.None, "Size");

            var CopyMemoryILGenerator = CopyMemoryMethodBuilder.GetILGenerator();

            var Copy8 = CopyMemoryILGenerator.DefineLabel();
            var Copy2 = CopyMemoryILGenerator.DefineLabel();
            var Copy1 = CopyMemoryILGenerator.DefineLabel();
            var Loop8 = CopyMemoryILGenerator.DefineLabel();
            var Return = CopyMemoryILGenerator.DefineLabel();

            CopyMemoryILGenerator.Emit(OpCodes.Br_S, Loop8);
            CopyMemoryILGenerator.MarkLabel(Copy8);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit_Ldc_I4(8);
            CopyMemoryILGenerator.Emit(OpCodes.Sub);
            CopyMemoryILGenerator.Emit_Starg(2);
            CopyMemoryILGenerator.Emit_Ldarg(1);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Add);
            CopyMemoryILGenerator.Emit_Ldarg(0);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Add);
            CopyMemoryILGenerator.Emit(OpCodes.Ldind_I8);
            CopyMemoryILGenerator.Emit(OpCodes.Stind_I8);
            CopyMemoryILGenerator.MarkLabel(Loop8);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit_Ldc_I4(8);
            CopyMemoryILGenerator.Emit(OpCodes.Bge_S, Copy8);

            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit_Ldc_I4(4);
            CopyMemoryILGenerator.Emit(OpCodes.Blt_S, Copy2);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit_Ldc_I4(4);
            CopyMemoryILGenerator.Emit(OpCodes.Sub);
            CopyMemoryILGenerator.Emit_Starg(2);
            CopyMemoryILGenerator.Emit_Ldarg(1);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Add);
            CopyMemoryILGenerator.Emit_Ldarg(0);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Add);
            CopyMemoryILGenerator.Emit(OpCodes.Ldind_I4);
            CopyMemoryILGenerator.Emit(OpCodes.Stind_I4);


            CopyMemoryILGenerator.MarkLabel(Copy2);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit_Ldc_I4(2);
            CopyMemoryILGenerator.Emit(OpCodes.Blt_S, Copy1);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit_Ldc_I4(2);
            CopyMemoryILGenerator.Emit(OpCodes.Sub);
            CopyMemoryILGenerator.Emit_Starg(2);
            CopyMemoryILGenerator.Emit_Ldarg(1);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Add);
            CopyMemoryILGenerator.Emit_Ldarg(0);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Add);
            CopyMemoryILGenerator.Emit(OpCodes.Ldind_I2);
            CopyMemoryILGenerator.Emit(OpCodes.Stind_I2);


            CopyMemoryILGenerator.MarkLabel(Copy1);
            CopyMemoryILGenerator.Emit_Ldarg(2);
            CopyMemoryILGenerator.Emit(OpCodes.Brfalse_S, Return);
            CopyMemoryILGenerator.Emit_Ldarg(1);
            CopyMemoryILGenerator.Emit_Ldarg(0);
            CopyMemoryILGenerator.Emit(OpCodes.Ldind_I2);
            CopyMemoryILGenerator.Emit(OpCodes.Stind_I2);


            CopyMemoryILGenerator.MarkLabel(Return);
            CopyMemoryILGenerator.Emit(OpCodes.Ret);

            #endregion


            #region -- GetValue<TValue>(IntPtr)。

            var GetValueMethodBuilder = PointerTypeBuilder.DefineMethod(
                "GetValue",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard,
                typeof(void),
                new Type[] { typeof(IntPtr) });

            var GetValueGenericParameters = GetValueMethodBuilder.DefineGenericParameters(new string[] { "TValue" });

            GetValueMethodBuilder.SetReturnType(GetValueGenericParameters[0]);

            GetValueMethodBuilder.DefineParameter(1, ParameterAttributes.None, "Pointer");

            var GetValueILGenerator = GetValueMethodBuilder.GetILGenerator();

            GetValueILGenerator.Emit_Ldarg(0);
            GetValueILGenerator.Emit(OpCodes.Ldobj, GetValueGenericParameters[0]);
            GetValueILGenerator.Emit(OpCodes.Ret);

            #endregion

            #region -- SetValue<TValue>(IntPtr, TValue)。

            var SetValueMethodBuilder = PointerTypeBuilder.DefineMethod(
                "SetValue",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.Static, CallingConventions.Standard);

            var SetValueGenericParameters = SetValueMethodBuilder.DefineGenericParameters(new string[] { "TValue" });

            SetValueMethodBuilder.SetParameters(new Type[] { typeof(IntPtr), SetValueGenericParameters[0] });

            SetValueMethodBuilder.SetReturnType(typeof(void));

            SetValueMethodBuilder.DefineParameter(1, ParameterAttributes.None, "Pointer");
            SetValueMethodBuilder.DefineParameter(2, ParameterAttributes.None, "Value");

            var SetValueILGenerator = SetValueMethodBuilder.GetILGenerator();

            SetValueILGenerator.Emit_Ldarg(0);
            SetValueILGenerator.Emit_Ldarg(1);
            SetValueILGenerator.Emit(OpCodes.Stobj, SetValueGenericParameters[0]);
            SetValueILGenerator.Emit(OpCodes.Ret);

            #endregion


            PointerTypeBuilder.CreateType();

            AssBuilder.Save("Core.Pointer.dll");
        }
    }
}